﻿using System;
using System.Diagnostics;

namespace SfsTools.Core
{
    /// <summary>
    /// Validates the index area.
    /// </summary>
    internal class IndexAreaListBuilderVisitor : Object, IIndexEntryVisitor
    {
        #region Properties

        /// <summary>
        /// Contains the file system.
        /// </summary>
        private readonly FileSystem FFileSystem;
        /// <summary>
        /// Contains the index area.
        /// </summary>
        private readonly IndexArea FIndexArea;
        /// <summary>
        /// Contains the index of the current entry.
        /// </summary>
        private int FCurrent;
        /// <summary>
        /// Contains the expected count of continuation entries.
        /// </summary>
        private int FExpectedContinuationEntries;
        /// <summary>
        /// Contains the name buffer for the current entry.
        /// </summary>
        private byte[] FEntryName;
        /// <summary>
        /// Contains the index into <see cref="FEntryName"/>.
        /// </summary>
        private int FEntryNameIndex;
        /// <summary>
        /// Contains the root directory.
        /// </summary>
        private readonly RootDirectory FRootDirectory;
        /// <summary>
        /// Contains the current callback.
        /// </summary>
        private IContinuationCompletionCallback FCallback;
        /// <summary>
        /// Contains the full path to <see cref="Directory"/> mapping.
        /// </summary>
        private readonly System.Collections.Generic.Dictionary<String, Directory> FDirectories = new System.Collections.Generic.Dictionary<String, Directory>();
        /// <summary>
        /// Contains the full path to <see cref="File"/> mapping.
        /// </summary>
        private readonly System.Collections.Generic.Dictionary<String, File> FFiles = new System.Collections.Generic.Dictionary<String, File>();

        #endregion

        #region Methods

        /// <summary>
        /// Performs the validation.
        /// </summary>
        //[DebuggerStepThrough]
        public void BuildContents()
        {
            for (int i = 0; i < FIndexArea.Length; i++)
            {
                FCurrent = i;
                IndexEntry entry = FIndexArea[i];
                if (entry != null)
                    entry.AcceptVisitor(this);
            }
        }

        /// <summary>
        /// Ensures that the directory with the specified name exists.
        /// </summary>
        /// <param name="name">The full path of the directory to check.</param>
        /// <returns>The directory that contains <paramref name="name"/> (ie. its parent directory).</returns>
        [DebuggerStepThrough]
        private Directory EnsureDirectoryExists(String name)
        {
            return EnsureDirectoryExists(name, false);
        }

        /// <summary>
        /// Ensures that the directory with the specified name exists.
        /// </summary>
        /// <param name="name">The full path of the directory to check.</param>
        /// <param name="create">Controls whether missing directories are created.</param>
        /// <returns>The directory that contains <paramref name="name"/> (ie. its parent directory).</returns>
        private Directory EnsureDirectoryExists(String name, bool create)
        {
            int index = name.LastIndexOf(System.IO.Path.DirectorySeparatorChar);
            Debug.Assert(index >= 0);
            if (index <= 0)
            {
                // File or directory in root.
                return FRootDirectory;
            }
            else
            {
                String parent = name.Substring(0, index);
                Directory directory;
                if (FDirectories.TryGetValue(parent, out directory))
                {
                    // Already exists.
                    return directory;
                }
                else
                {
                    // Doesnt exists. Check parent.
                    Directory directory2 = EnsureDirectoryExists(parent, true);
                    // Create directory.
                    if (create)
                    {
                        directory = directory2.CreateDirectory(name.Substring(index + 1));
                        FDirectories.Add(name, directory);
                        return directory;
                    }
                    else
                    {
                        return directory2;
                    }
                }
            }
        }

        /// <summary>
        /// Adds <paramref name="directory"/> to the list of directories.
        /// </summary>
        /// <param name="directory">The <see cref="Directory"/> to add.</param>
        [DebuggerStepThrough]
        private void Add(Directory directory)
        {
            String fullPath = directory.GetFullPath();
            if (FDirectories.ContainsKey(fullPath))
            {
                // Duplicate.
                throw new DuplicateDirectoryException(fullPath);
            }
            else if (FFiles.ContainsKey(fullPath))
            {
                // Duplicate.
                throw new DirectoryAlreadyDefinedAsFileException(fullPath);
            }
            else
            {
                FDirectories.Add(fullPath, directory);
            }
        }

        /// <summary>
        /// Adds <paramref name="file"/> to the list of directories.
        /// </summary>
        /// <param name="file">The <see cref="File"/> to add.</param>
        [DebuggerStepThrough]
        private void Add(File file)
        {
            String fullPath = file.GetFullPath();
            if (FFiles.ContainsKey(fullPath))
            {
                // Duplicate.
                throw new DuplicateFileException(fullPath);
            }
            else if (FDirectories.ContainsKey(fullPath))
            {
                // Duplicate.
                throw new FileAlreadyDefinedAsDirectoryException(fullPath);
            }
            else
            {
                FFiles.Add(fullPath, file);
            }
        }

        #endregion

        #region Constructors
        /// <overloads>Initializes a new instance of <see cref="IndexAreaListBuilderVisitor"/>.</overloads>
        /// <summary>
        /// Initializes a new instance of <see cref="IndexAreaListBuilderVisitor"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        [DebuggerStepThrough]
        public IndexAreaListBuilderVisitor(FileSystem fs)
            : base()
        {
            FFileSystem = fs;
            FIndexArea = fs.IndexArea;
            FRootDirectory = (RootDirectory)fs.RootDirectory;
            Add(FRootDirectory);
        }
        #endregion

        #region IIndexEntryVisitor Members

        /// <summary>
        /// Called by <see cref="BadSectorEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        [DebuggerStepThrough]
        void IIndexEntryVisitor.VisitBadSector(BadSectorEntry entry)
        {
            // Ignore.
        }

        /// <summary>
        /// Called by <see cref="ContinuationEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        [DebuggerStepThrough]
        void IIndexEntryVisitor.VisitContinuation(ContinuationEntry entry)
        {
            Debug.Assert(FExpectedContinuationEntries > 0);
            // Copy contents into name buffer.
            entry.CopyTo(FEntryName, FEntryNameIndex);
            FEntryNameIndex += 64;
            FExpectedContinuationEntries--;
            // Invoke callback if all continuation entries found.
            if (FExpectedContinuationEntries == 0)
            {
                FCallback.OnFinished(FEntryName);
                FCallback = null;
            }
        }

        /// <summary>
        /// Called by <see cref="PresentDirectoryEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        //[DebuggerStepThrough]
        void IIndexEntryVisitor.VisitPresentDirectory(PresentDirectoryEntry entry)
        {
            FExpectedContinuationEntries = entry.ContinuationCount;
            // Prepare name buffer.
            FEntryName = new byte[FExpectedContinuationEntries * 64 + 54];
            FEntryNameIndex = 0;
            entry.CopyTo(FEntryName, FEntryNameIndex);
            FEntryNameIndex += 54;
            FCallback = new CreatePresentDirectoryCallback(this, entry, FCurrent);
            // Invoke callback if all continuation entries found.
            if (FExpectedContinuationEntries == 0)
            {
                FCallback.OnFinished(FEntryName);
                FCallback = null;
            }
        }

        /// <summary>
        /// Called by <see cref="PresentFileEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        //[DebuggerStepThrough]
        void IIndexEntryVisitor.VisitPresentFile(PresentFileEntry entry)
        {
            FExpectedContinuationEntries = entry.ContinuationCount;
            // Prepare name buffer.
            FEntryName = new byte[FExpectedContinuationEntries * 64 + 30];
            FEntryNameIndex = 0;
            entry.CopyTo(FEntryName, FEntryNameIndex);
            FEntryNameIndex += 30;
            FCallback = new CreatePresentFileCallback(this, entry, FCurrent);
            // Invoke callback if all continuation entries found.
            if (FExpectedContinuationEntries == 0)
            {
                FCallback.OnFinished(FEntryName);
                FCallback = null;
            }
        }

        /// <summary>
        /// Called by <see cref="DeletedDirectoryEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        [DebuggerStepThrough]
        void IIndexEntryVisitor.VisitDeletedDirectory(DeletedDirectoryEntry entry)
        {
            FExpectedContinuationEntries = entry.ContinuationCount;
            // Prepare name buffer.
            FEntryName = new byte[FExpectedContinuationEntries * 64 + 54];
            FEntryNameIndex = 0;
            entry.CopyTo(FEntryName, FEntryNameIndex);
            FEntryNameIndex += 54;
            FCallback = new CreateDeletedDirectoryCallback(this, entry, FCurrent);
            // Invoke callback if all continuation entries found.
            if (FExpectedContinuationEntries == 0)
            {
                FCallback.OnFinished(FEntryName);
                FCallback = null;
            }
        }

        /// <summary>
        /// Called by <see cref="DeletedFileEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        [DebuggerStepThrough]
        void IIndexEntryVisitor.VisitDeletedFile(DeletedFileEntry entry)
        {
            FExpectedContinuationEntries = entry.ContinuationCount;
            // Prepare name buffer.
            FEntryName = new byte[FExpectedContinuationEntries * 64 + 30];
            FEntryNameIndex = 0;
            entry.CopyTo(FEntryName, FEntryNameIndex);
            FEntryNameIndex += 30;
            FCallback = new CreateDeletedFileCallback(this, entry, FCurrent);
            // Invoke callback if all continuation entries found.
            if (FExpectedContinuationEntries == 0)
            {
                FCallback.OnFinished(FEntryName);
                FCallback = null;
            }
        }

        /// <summary>
        /// Called by <see cref="StartingMarkerEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        [DebuggerStepThrough]
        void IIndexEntryVisitor.VisitStartingMarker(StartingMarkerEntry entry)
        {
            // Ignore.
        }

        /// <summary>
        /// Called by <see cref="UnknownIndexEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        [DebuggerStepThrough]
        void IIndexEntryVisitor.VisitUnknown(UnknownIndexEntry entry)
        {
            // Ignore.
            // Invoke callback if all continuation entries found.
            if (FExpectedContinuationEntries > 0)
            {
                FCallback.OnFinished(FEntryName);
                FCallback = null;
                FExpectedContinuationEntries = 0;
            }
        }

        /// <summary>
        /// Called by <see cref="UnusedIndexEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        [DebuggerStepThrough]
        void IIndexEntryVisitor.VisitUnused(UnusedIndexEntry entry)
        {
            // Ignore.
            // Invoke callback if all continuation entries found.
            if (FExpectedContinuationEntries > 0)
            {
                FCallback.OnFinished(FEntryName);
                FCallback = null;
                FExpectedContinuationEntries = 0;
            }
        }

        /// <summary>
        /// Called by <see cref="VolumeIdentifierEntry"/> objects.
        /// </summary>
        /// <param name="entry">The entry to visit.</param>
        [DebuggerStepThrough]
        void IIndexEntryVisitor.VisitVolumeIdentifier(VolumeIdentifierEntry entry)
        {
            // Ignore.
            // Invoke callback if all continuation entries found.
            if (FExpectedContinuationEntries > 0)
            {
                FCallback.OnFinished(FEntryName);
                FCallback = null;
                FExpectedContinuationEntries = 0;
            }
        }

        #endregion

        #region Helper classes

        #region Callback interface

        /// <summary>
        /// Callback to call when all continuation entries have been parsed.
        /// </summary>
        private interface IContinuationCompletionCallback
        {
            /// <summary>
            /// Invoked when all continuation entries have been parsed.
            /// </summary>
            /// <param name="buffer">The buffer containing the name.</param>
            void OnFinished(byte[] buffer);
        }

        #endregion

        #region Base class

        /// <summary>
        /// Provides common functionality for ICCC objects.
        /// </summary>
        private class ContinuationCompletionCallbackBase : Object
        {
            #region Properties

            /// <summary>
            /// Contains the parent instance.
            /// </summary>
            protected readonly IndexAreaListBuilderVisitor FParent;
            /// <summary>
            /// The zero-based index of the index area entry.
            /// </summary>
            protected readonly int FIndex;

            #endregion

            #region Methods

            /// <summary>
            /// Validates the name of an entry and converts it to a <see cref="String"/>.
            /// </summary>
            /// <param name="buffer">The array containing the name of the entry.</param>
            /// <returns></returns>
            [DebuggerStepThrough]
            protected static String ValidateName(byte[] buffer)
            {
                // Locate terminator zero byte.
                int index;
                for (index = 0; (index < buffer.Length) && (buffer[index] != 0); index++)
                {
                    // Filter out bad names.
                    if (buffer[index] < 0x20)
                        throw new InvalidUtf8NameException();
                    // Also prevent these values.
                    if ((buffer[index] == 0x22) ||
                        // " (double quote, 0x0022)
                        (buffer[index] == 0x2A) ||
                        // * (asterix, 0x002A)
                        (buffer[index] == 0x3A) ||
                        // : (colon, 0x003A)
                        (buffer[index] == 0x3C) ||
                        // < (less than sign, 0x003C)
                        (buffer[index] == 0x3E) ||
                        // > (greater than sign, 0x003E)
                        (buffer[index] == 0x3F) ||
                        // ? (question mark, 0x003F)
                        (buffer[index] == 0x5C) ||
                        // \ (backward slash, 0x005C)
                        (buffer[index] == 0x7F) ||
                        // DEL (delete, 0x007F)
                        (buffer[index] == 0xA0)
                        // NBSP (no break space character, 0x00A0)
                        )
                    {
                        throw new InvalidUtf8NameException();
                    }
                }
                String name;
                try
                {
                    // TODO: does this detect invalid UTF8 byte sequences?
                    name = System.Text.Encoding.UTF8.GetString(buffer, 0, index);
                }
                catch (Exception ex)
                {
                    throw new InvalidUtf8NameException(ex);
                }
                return name;
            }

            /// <summary>
            /// Normalizes <paramref name="name"/> (prepends leading separator, removes trailing separator).
            /// </summary>
            /// <param name="name">The name of an entry.</param>
            /// <returns></returns>
            [DebuggerStepThrough]
            protected static String NormalizeName(String name)
            {
                // Prepend / if necessary to normalize the name.
                if (!name.StartsWith(new String('/', 1)))
                {
                    name = String.Concat('/', name);
                    //throw new InvalidDirectoryEntryException(1);
                }
                // Convert to local path separator.
                Debug.Assert((name == String.Empty) || name.StartsWith(new String('/', 1)));
                if (name.EndsWith(new String('/', 1)))
                    name = name.Substring(0, name.Length - 1).Replace('/', System.IO.Path.DirectorySeparatorChar);
                else
                    name = name.Replace('/', System.IO.Path.DirectorySeparatorChar);
                if (name == String.Empty)
                {
                    // Empty names are not allowed.
                    throw new DuplicateDirectoryException();
                }
                return name;
            }

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="ContinuationCompletionCallbackBase"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="ContinuationCompletionCallbackBase"/>.
            /// </summary>
            /// <param name="parent">The parent instance.</param>
            /// <param name="index">The zero-based index of this index area entry.</param>
            [DebuggerStepThrough]
            protected ContinuationCompletionCallbackBase(IndexAreaListBuilderVisitor parent, int index)
                : base()
            {
                FParent = parent;
                FIndex = index;
            }
            #endregion
        }

        #endregion

        #region Create present directory

        /// <summary>
        /// Callback that creates a present directory entry.
        /// </summary>
        private class CreatePresentDirectoryCallback : ContinuationCompletionCallbackBase, IContinuationCompletionCallback
        {
            #region Properties

            /// <summary>
            /// The index area entry.
            /// </summary>
            private PresentDirectoryEntry FEntry;

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="CreatePresentDirectoryCallback"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="CreatePresentDirectoryCallback"/>.
            /// </summary>
            /// <param name="parent">The parent instance.</param>
            /// <param name="entry">The entry.</param>
            /// <param name="index">The zero-based index of this index area entry.</param>
            [DebuggerStepThrough]
            public CreatePresentDirectoryCallback(IndexAreaListBuilderVisitor parent, PresentDirectoryEntry entry, int index)
                : base(parent, index)
            {
                FEntry = entry;
            }
            #endregion

            #region ICCC Members

            /// <summary>
            /// Invoked when all continuation entries have been parsed.
            /// </summary>
            /// <param name="buffer">The buffer containing the name.</param>
            void IContinuationCompletionCallback.OnFinished(byte[] buffer)
            {
                String name = ValidateName(buffer);
                name = NormalizeName(name);
                int index = name.LastIndexOf(System.IO.Path.DirectorySeparatorChar);
                if ((index == name.Length - 3) && (name.EndsWith(new String('.', 2))))
                {
                    // The specification states that entries for '.' and '..' (this and parent directories)
                    // are never stored, they are implied to exist. Disallow these entries.
                    throw new InvalidDirectoryEntryException(3);
                }
                if ((index == name.Length - 2) && (name.EndsWith(new String('.', 1))))
                {
                    // The specification states that entries for '.' and '..' (this and parent directories)
                    // are never stored, they are implied to exist. Disallow these entries.
                    throw new InvalidDirectoryEntryException(2);
                }
                // Extract name of THIS directory, and path to parent directory.
                Directory parent = FParent.EnsureDirectoryExists(name);
                // Create directory.
                Debug.Assert(index >= 0);
                String dirName = name.Substring(index + 1);
                Directory directory = new Directory(FParent.FFileSystem, parent, dirName, FIndex, false);
                // Add to ListBuilder first to properly filter out duplicates and other cases.
                FParent.Add(directory);
                parent.Add(directory);
                // TODO: check that all parent directories are marked as Present.
            }

            #endregion
        }

        #endregion

        #region Create deleted directory

        /// <summary>
        /// Callback that creates a deleted directory entry.
        /// </summary>
        private class CreateDeletedDirectoryCallback : ContinuationCompletionCallbackBase, IContinuationCompletionCallback
        {
            #region Properties

            /// <summary>
            /// The index area entry.
            /// </summary>
            private DeletedDirectoryEntry FEntry;

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="CreateDeletedDirectoryCallback"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="CreateDeletedDirectoryCallback"/>.
            /// </summary>
            /// <param name="parent">The parent instance.</param>
            /// <param name="entry">The entry.</param>
            /// <param name="index">The zero-based index of this index area entry.</param>
            [DebuggerStepThrough]
            public CreateDeletedDirectoryCallback(IndexAreaListBuilderVisitor parent, DeletedDirectoryEntry entry, int index)
                : base(parent, index)
            {
                FEntry = entry;
            }
            #endregion

            #region ICCC Members

            /// <summary>
            /// Invoked when all continuation entries have been parsed.
            /// </summary>
            /// <param name="buffer">The buffer containing the name.</param>
            void IContinuationCompletionCallback.OnFinished(byte[] buffer)
            {
                String name = ValidateName(buffer);
                name = NormalizeName(name);
                // The specification states that entries for '.' and '..' (this and parent directories)
                // are never stored, they are implied to exist. So it is an error to have a '/' or '' directory.
                if (name.EndsWith(new String('.', 1)) || name.EndsWith(new String('.', 2)))
                {
                    // The specification states that entries for '.' and '..' (this and parent directories)
                    // are never stored, they are implied to exist. Disallow these entries.
                    throw new InvalidIndexAreaException(String.Format("Cant have a directory with name \"{0}\"", name));
                }
                // Extract name of THIS directory, and path to parent directory.
                Directory parent = FParent.EnsureDirectoryExists(name);
                // Create directory.
                int index = name.LastIndexOf(System.IO.Path.DirectorySeparatorChar);
                Debug.Assert(index >= 0);
                String dirName = name.Substring(index + 1);
#if false
                Directory directory = new Directory(FParent.FFileSystem, parent, dirName, FIndex, true);
                // Add to ListBuilder first to properly filter out duplicates and other cases.
                FParent.Add(directory);
                parent.Add(directory);
                // TODO: check that all parent directories are marked as Present?
#endif
            }

            #endregion
        }

        #endregion

        #region Create present file

        /// <summary>
        /// Callback that creates a present file entry.
        /// </summary>
        private class CreatePresentFileCallback : ContinuationCompletionCallbackBase, IContinuationCompletionCallback
        {
            #region Properties

            /// <summary>
            /// The index area entry.
            /// </summary>
            private PresentFileEntry FEntry;

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="CreatePresentFileCallback"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="CreatePresentFileCallback"/>.
            /// </summary>
            /// <param name="parent">The parent instance.</param>
            /// <param name="entry">The entry.</param>
            /// <param name="index">The zero-based index of this index area entry.</param>
            [DebuggerStepThrough]
            public CreatePresentFileCallback(IndexAreaListBuilderVisitor parent, PresentFileEntry entry, int index)
                : base(parent, index)
            {
                FEntry = entry;
            }
            #endregion

            #region ICCC Members

            /// <summary>
            /// Invoked when all continuation entries have been parsed.
            /// </summary>
            /// <param name="buffer">The buffer containing the name.</param>
            void IContinuationCompletionCallback.OnFinished(byte[] buffer)
            {
                String name = ValidateName(buffer);
                name = NormalizeName(name);
                // Extract name of THIS directory, and path to parent directory.
                Directory parent = FParent.EnsureDirectoryExists(name);
                // Create directory.
                int index = name.LastIndexOf(System.IO.Path.DirectorySeparatorChar);
                Debug.Assert(index >= 0);
                String fileName = name.Substring(index + 1);
                File file = new File(FParent.FFileSystem, parent, fileName, FIndex, false);
                // Add to ListBuilder first to properly filter out duplicates and other cases.
                FParent.Add(file);
                parent.Add(file);
                // TODO: check that all parent directories are marked as Present.
            }

            #endregion
        }

        #endregion

        #region Create deleted file

        /// <summary>
        /// Callback that creates a deleted file entry.
        /// </summary>
        private class CreateDeletedFileCallback : ContinuationCompletionCallbackBase, IContinuationCompletionCallback
        {
            #region Properties

            /// <summary>
            /// The index area entry.
            /// </summary>
            private DeletedFileEntry FEntry;

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="CreateDeletedFileCallback"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="CreateDeletedFileCallback"/>.
            /// </summary>
            /// <param name="parent">The parent instance.</param>
            /// <param name="entry">The entry.</param>
            /// <param name="index">The zero-based index of this index area entry.</param>
            [DebuggerStepThrough]
            public CreateDeletedFileCallback(IndexAreaListBuilderVisitor parent, DeletedFileEntry entry, int index)
                : base(parent, index)
            {
                FEntry = entry;
            }
            #endregion

            #region ICCC Members

            /// <summary>
            /// Invoked when all continuation entries have been parsed.
            /// </summary>
            /// <param name="buffer">The buffer containing the name.</param>
            void IContinuationCompletionCallback.OnFinished(byte[] buffer)
            {
                String name = ValidateName(buffer);
                name = NormalizeName(name);
                // Extract name of THIS directory, and path to parent directory.
                Directory parent = FParent.EnsureDirectoryExists(name);
                // Create directory.
                int index = name.LastIndexOf(System.IO.Path.DirectorySeparatorChar);
                Debug.Assert(index >= 0);
                String fileName = name.Substring(index + 1);
#if false
                File file = new File(FParent.FFileSystem, parent, fileName, FIndex, true);
                // Add to ListBuilder first to properly filter out duplicates and other cases.
                FParent.Add(file);
                parent.Add(file);
                // TODO: check that all parent directories are marked as Present.
#endif
            }

            #endregion
        }

        #endregion

        #endregion
    }
}
